﻿using UnityEngine;
using DG.Tweening;
public class CameraControl : MonoBehaviour {

    public float DampTime = .2f;
    public float ScreenEdgeBuffer = 4f;
    public float MinSize = 6.5f;
    public Vector3 Offset;

    [HideInInspector]
    public Transform[] Targets;

    private Camera _camera;
    private float _zoomSpeed;
    private Vector3 _moveVelocity;
    private Vector3 _desiredPosition;

    public Rect m_AreaRect;

    private void Awake()
    {
        _camera = GetComponentInChildren<Camera>();

        
    }

    //必须在FixedUpdate移动，否则在跟随的时候会很不流畅，一卡一卡的
    private void FixedUpdate()
    {
        Move();
        Zoom();
    }

    void OnDrawGizmos()
    {
        _camera = GetComponentInChildren<Camera>();
        float far = _camera.farClipPlane;

        /*
        Gizmos.color = Color.red;
        Gizmos.DrawLine(transform.position, Targets[0].transform.position);

        Vector3 start1 = _camera.ViewportToWorldPoint(new Vector3(0, 0, 0));
        Vector3 start2 = _camera.ViewportToWorldPoint(new Vector3(0, 1, 0));
        Vector3 start3 = _camera.ViewportToWorldPoint(new Vector3(1, 1, 0));
        Vector3 start4 = _camera.ViewportToWorldPoint(new Vector3(1, 0, 0));

        Vector3 end1 = _camera.ViewportToWorldPoint(new Vector3(0, 0, far));
        Vector3 end2 = _camera.ViewportToWorldPoint(new Vector3(0, 1, far));
        Vector3 end3 = _camera.ViewportToWorldPoint(new Vector3(1, 1, far));
        Vector3 end4 = _camera.ViewportToWorldPoint(new Vector3(1, 0, far));

        Vector3 intersectPoint1;
        Vector3 intersectPoint2;
        Vector3 intersectPoint3;
        Vector3 intersectPoint4;
        LinePlaneIntersection(out intersectPoint1, start1, (end1-start1).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint1, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoint2, start2, (end2 - start2).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint2, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoint3, start3, (end3 - start3).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint3, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoint4, start4, (end4 - start4).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint4, Vector3.up * 3);

        //Debug.Log(intersectPoint1 + "," + intersectPoint2 + "," + intersectPoint3 + "," + intersectPoint4);

        Gizmos.DrawLine(intersectPoint1, intersectPoint2);
        Gizmos.DrawLine(intersectPoint2, intersectPoint3);
        Gizmos.DrawLine(intersectPoint3, intersectPoint4);
        Gizmos.DrawLine(intersectPoint4, intersectPoint1);

        Gizmos.DrawLine(intersectPoint1, intersectPoint3);
        Gizmos.DrawLine(intersectPoint2, intersectPoint4);

        Vector3 center;
        LinePlaneIntersection(out center, 
            transform.position,
            transform.TransformDirection(Vector3.forward), 
            Vector3.up,
            Targets[0].position);
        Gizmos.DrawSphere(center, .1f);
        
        */

        float h = _camera.orthographicSize * 2;
        float w = h * _camera.aspect;

        //在摄像机中心往摄像机正面投射一条绿线
        Gizmos.color = Color.green;
        Gizmos.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * far);

        //画出摄像机可移动的范围
        Vector3 p1 = new Vector3(m_AreaRect.min.x, 0, m_AreaRect.max.y);
        Vector3 p2 = new Vector3(m_AreaRect.max.x, 0, m_AreaRect.max.y);
        Vector3 p3 = new Vector3(m_AreaRect.max.x, 0, m_AreaRect.min.y);
        Vector3 p4 = new Vector3(m_AreaRect.min.x, 0, m_AreaRect.min.y);
        Gizmos.DrawLine(p1, p2);
        Gizmos.DrawLine(p2, p3);
        Gizmos.DrawLine(p3, p4);
        Gizmos.DrawLine(p4, p1);
        //Debug.Log(m_AreaRect.min+","+m_AreaRect.max);
    }

    public void MoveTo(Vector3 target)
    {
        transform.DOMove(target, 1f);
    }

    private void Move()
    {
        FindAveragePosition();
        Vector3 tryPosition = Vector3.SmoothDamp(transform.position,
            new Vector3(_desiredPosition.x + Offset.x, _desiredPosition.y + Offset.y, _desiredPosition.z + Offset.z),
            ref _moveVelocity,
            DampTime);

        Vector3 nextPosition;
        JudgeInsideBorder(out nextPosition, tryPosition);
        transform.position = nextPosition;
    }

    private void FindAveragePosition()
    {
        if (Targets == null) return;

        Vector3 averagePos = new Vector3();
        int numTargets = 0;

        for (int i = 0; i < Targets.Length; i++)
        {
            if (Targets[i] == null) return;

            if (!Targets[i].gameObject.activeSelf)
                continue;

            averagePos += Targets[i].position;
            numTargets++;
        }

        if (numTargets > 0)
            averagePos /= numTargets;

        averagePos.y = transform.position.y;

        _desiredPosition = averagePos;
    }

    private void Zoom()
    {
        float requiredSize = FindRequiredSize();
        _camera.orthographicSize = Mathf.SmoothDamp(_camera.orthographicSize, requiredSize, ref _zoomSpeed, DampTime);
    }

    private float FindRequiredSize()
    {
        //世界中的_desiredPosition相对于本物体的坐标系下是一个什么位置
        Vector3 desiredLocalPos = transform.InverseTransformPoint(_desiredPosition);

        float size = 0f;

        for (int i = 0; i < Targets.Length; i++)
        {
            if (Targets[i] == null) break;

            if (!Targets[i].gameObject.activeSelf)
                continue;

            Vector3 targetLocalPos = transform.InverseTransformPoint(Targets[i].position);

            Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;

            size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));

            size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / _camera.aspect);
        }

        size += ScreenEdgeBuffer;

        size = Mathf.Max(size, MinSize);

        return size;
    }

    public void SetStartPositionAndSize()
    {
        FindAveragePosition();

        transform.position = _desiredPosition;

        _camera.orthographicSize = FindRequiredSize();
    }

    //Get the intersection between a line and a plane. 
    //If the line and plane are not parallel, the function outputs true, otherwise false.
    public static bool LinePlaneIntersection(out Vector3 intersection, Vector3 linePoint, Vector3 lineVec, Vector3 planeNormal, Vector3 planePoint)
    {

        float length;
        float dotNumerator;
        float dotDenominator;
        Vector3 vector;
        intersection = Vector3.zero;

        //calculate the distance between the linePoint and the line-plane intersection point
        dotNumerator = Vector3.Dot((planePoint - linePoint), planeNormal);
        dotDenominator = Vector3.Dot(lineVec, planeNormal);

        //line and plane are not parallel
        if (dotDenominator != 0.0f)
        {
            length = dotNumerator / dotDenominator;

            //create a vector from the linePoint to the intersection point
            vector = SetVectorLength(lineVec, length);

            //get the coordinates of the line-plane intersection point
            intersection = linePoint + vector;

            return true;
        }

        //output not valid
        else {
            return false;
        }
    }

    //Calculate the intersection point of two lines. Returns true if lines intersect, otherwise false.
    //Note that in 3d, two lines do not intersect most of the time. So if the two lines are not in the 
    //same plane, use ClosestPointsOnTwoLines() instead.
    public static bool LineLineIntersection(out Vector3 intersection, Vector3 linePoint1, Vector3 lineVec1, Vector3 linePoint2, Vector3 lineVec2)
    {

        Vector3 lineVec3 = linePoint2 - linePoint1;
        Vector3 crossVec1and2 = Vector3.Cross(lineVec1, lineVec2);
        Vector3 crossVec3and2 = Vector3.Cross(lineVec3, lineVec2);

        float planarFactor = Vector3.Dot(lineVec3, crossVec1and2);

        //is coplanar, and not parrallel
        if (Mathf.Abs(planarFactor) < 0.0001f && crossVec1and2.sqrMagnitude > 0.0001f)
        {
            float s = Vector3.Dot(crossVec3and2, crossVec1and2) / crossVec1and2.sqrMagnitude;
            intersection = linePoint1 + (lineVec1 * s);
            return true;
        }
        else
        {
            intersection = Vector3.zero;
            return false;
        }
    }

    //create a vector of direction "vector" with length "size"
    public static Vector3 SetVectorLength(Vector3 vector, float size)
    {

        //normalize the vector
        Vector3 vectorNormalized = Vector3.Normalize(vector);

        //scale the vector
        return vectorNormalized *= size;
    }

    //不计算y轴
    private bool JudgeInsideBorder(out Vector3 clampedPostion,Vector3 tryPosition)
    {
        float far = _camera.farClipPlane;
        float h = _camera.orthographicSize * 2;
        float w = h * _camera.aspect;

        Vector3 start1 = _camera.ViewportToWorldPoint(new Vector3(0, 0, 0));
        Vector3 start2 = _camera.ViewportToWorldPoint(new Vector3(0, 1, 0));
        Vector3 start3 = _camera.ViewportToWorldPoint(new Vector3(1, 1, 0));
        Vector3 start4 = _camera.ViewportToWorldPoint(new Vector3(1, 0, 0));

        Vector3 end1 = _camera.ViewportToWorldPoint(new Vector3(0, 0, far));
        Vector3 end2 = _camera.ViewportToWorldPoint(new Vector3(0, 1, far));
        Vector3 end3 = _camera.ViewportToWorldPoint(new Vector3(1, 1, far));
        Vector3 end4 = _camera.ViewportToWorldPoint(new Vector3(1, 0, far));

        Vector3[] intersectPoints = new Vector3[4];

        LinePlaneIntersection(out intersectPoints[0], start1, (end1 - start1).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint1, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoints[1], start2, (end2 - start2).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint2, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoints[2], start3, (end3 - start3).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint3, Vector3.up * 3);

        LinePlaneIntersection(out intersectPoints[3], start4, (end4 - start4).normalized, Vector3.up, Vector3.zero);
        //Gizmos.DrawRay(intersectPoint4, Vector3.up * 3);

        Vector3 deltaAmount = tryPosition - transform.position;

        clampedPostion = new Vector3();
        if (deltaAmount.x != 0)
        {
            for (int i = 0; i < intersectPoints.Length; i++)
            {
                bool isInside = m_AreaRect.Contains(new Vector2(intersectPoints[i].x + deltaAmount.x, intersectPoints[i].z));
                //禁止继续往x轴方向移动
                if (!isInside)
                {
                    deltaAmount.x = 0;
                    break;
                }
            }

        }

        if (deltaAmount.z != 0)
        {
            for (int i = 0; i < intersectPoints.Length; i++)
            {
                bool isInside = m_AreaRect.Contains(new Vector2(intersectPoints[i].x, intersectPoints[i].z + deltaAmount.z));

                //禁止继续往x轴方向移动
                if (!isInside)
                {
                    deltaAmount.z = 0;
                    break;
                }
            }
        }

        clampedPostion = transform.position + deltaAmount;
        return true;
    }
}
